<?php
/**
 ***********************************************************************************************************************
 * 路由解析
 * @autor llq
 ***********************************************************************************************************************
 */

namespace artisan;

use mike\zipkin\Core;

/**
 * Class route
 * @package artisan
 */
class route
{
    /**
     * 路由分发
     *
     * @return mixed
     */
    public function dispatch()
    {
        Core::getInstance()->setServiceName(PROJECT_NAME)
            ->setZipkinUrl(config::get('zipkin')['URL_ZIPKIN'])
            ->beforeRequest();

        // 解析路由
        $segments = $this->fetch();

        // 设置默认首页
        if (trim(input::uri()) == '/' && empty($segments)) {
            $this->showDefault();
        }

        // 无法解析抛出异常
        if (empty($segments)) {
            $this->showError('Invalid Route');
        }

        // 初始化信息
        $handle = $segments['class'];
        $method = $segments['method'];
        $arguments = $segments['arguments'];

        // 控制请求方式 比如只允许POST请求
        if ($segments['allow_http_method'] && $segments['allow_http_method'] != input::method()) {
            $this->showError('Invalid HTTP type');
        }

        // 判断控制器类是否存在
        $class = str_replace('\\', '/', $handle);
        if (!file_exists(APPPATH . ltrim($class, '/') . '.php')) {
            // 尝试自动识别index方法
            $class = $class . '/' . $segments['method'];
            if (!file_exists(APPPATH . ltrim($class, '/') . '.php')) {
                $this->showError('Invalid class');
            }
            $handle = $handle . '\\' . $method;
            $method = 'index';
        }

        // 反射类信息
        $rclass = new \ReflectionClass($handle);

        // 接口和抽象类禁止直接访问
        if ($rclass->isAbstract() || $rclass->isInterface()) {
            $this->showError('Invalid class');
        }

        // 判断方法是否存在
        if (!$rclass->hasMethod($method)) {
            $this->showError('Invalid method');
        }

        // 保护、私有、静态方法不允许访问
        if (!$rclass->getMethod($method)->isPublic() && !$rclass->getMethod($method)->isFinal()) {
            $this->showError('Invalid method');
        }

        // 判断传入参数和方法参数是否一致
        $rarg = $rclass->getMethod($method)->getParameters();
        for ($i = 0; $i < count($rarg); $i++) {
            if (!$rarg[0]->allowsNull() && empty($arguments[$i])) {
                $this->showError('Invalid default arguments');
            }
        }

        // 如果传入参数，而方法中未设置参数直接报404
        if (!empty($arguments) && count($rarg) == 0) {
            $this->showError('Invalid default arguments');
        }

        // 实例化控制器
        $handle = new $handle;

        // 注入默认模板
        $classArr = array_filter(explode('/', str_replace('\\', '/', $segments['class'])));
        array_shift($classArr);
        output::setTemplate(ltrim(implode('/', $classArr) . '/' . $method, '/'));

        // 调用方法
        $result = $arguments ? call_user_func_array(array($handle, $method), $arguments) : call_user_func(array(
            $handle,
            $method
        ));

        if (is_array($result)) {
            output::setTemplateData($result);
        }

        // 隐式输出 转换null和bool类型为int类型
        output::flush((is_null($result) || is_bool($result)) ? intval($result) : $result);
    }


    /**
     * 解析路由规则
     *
     * @return array
     */
    public function fetch()
    {
        // 路由重写配置文件
        $rules = config::get('route');

        $namespace = '\\action\\';

        // 当前路由
        $currentUri = input::uri();

        // 在配置文件中找路由规则
        foreach ((array)$rules as $regx => $action) {

            // 特殊处理 去除前后空格
            $regx = '/' . trim(ltrim($regx, '/'));
            $action = ltrim(trim($action), '/');

            // 转换正则表达式特殊符号
            $regx = preg_replace('/\\[\/]|([^\/])\/|^\//', '$1\/', $regx);

            // 使用正则匹配规则
            if (preg_match('/^' . $regx . '$/', $currentUri, $regxArg)) {

                // 扔掉第一段
                array_shift($regxArg);

                // 针对路由404指向 如果重写规则设置404则返回404 如果设置的类和方法不对返回404
                if ($action == '404' || count(explode('/', $action)) < 2) {
                    return array();
                }

                // 处理class和method
                $params = explode('/', $action);
                // 最后一个值默认为方法和参数混合
                $method = array_pop($params);

                // 处理方法和参数
                $maArr = explode(':', $method);
                // 处理方法中的反向替换
                $method = $maArr[0];
                foreach ($regxArg as $key => $val) {
                    $method = str_replace('$' . ($key + 1), $val, $method);
                }

                // 处理参数 如/user/index:$1,$2这种
                // 初始化参数
                $arguments = array();
                if (isset($maArr[1])) {
                    if (preg_match_all('/[$]([1-9]{1})/', $maArr[1], $methodArg)) {
                        $methodArg = $methodArg[1];
                        foreach ($regxArg as $key => $val) {
                            if (isset($methodArg[$key])) {
                                $arguments[] = $val;
                            }
                        }
                    }
                }

                // 处理反向替换 获得最终的方法
                $method = preg_replace('/:(.*)$/', '', $method);

                // 处理允许的请求类型 同时取出类名
                if (preg_match('/^(POST|GET|DELETE|PUT|OPTION|HEADER):/', $params[0], $matches)) {
                    $allow_http_method = strtoupper($matches[1]);
                    $params[0] = preg_replace('/^(POST|GET|DELETE|PUT|OPTION|HEADER)\s*:\s*/', '', $params[0]);
                }

                // 获得最终类名
                $class = $namespace . implode('\\', $params);

                // 返回匹配结果
                return array(
                    'allow_http_method' => !empty($allow_http_method) ? $allow_http_method : '',
                    'class' => $class,
                    'method' => $method,
                    'arguments' => !empty($arguments) ? $arguments : array()
                );
            }

        }

        $uriLengthLimit = 2;

        // 1）尝试直接寻找action目录下对应方法
        $uriArr = explode('/', $currentUri);
        $uriArr = array_values(array_filter($uriArr));

        // 如果不合法直接舍弃
        if (empty($uriArr)) {
            return array();
        }

        // 尝试默认寻找index方法
        if (count($uriArr) < $uriLengthLimit) {
            array_push($uriArr, 'index');
        }

        // $uriArr最后一段
        $lastPiece = array_pop($uriArr);
        // 如果最后一位是数字，当成参数处理
        if (is_numeric($lastPiece)) {

            // 如果数组长度小于1，判断路由错误
            if (count($uriArr) < $uriLengthLimit - 1) {
                return array();
            }

            // 默认寻找index方法
            if (count($uriArr) == $uriLengthLimit - 1) {
                array_push($uriArr, 'index');
            }

            // 参数
            $arguments = $lastPiece;
            // 方法
            $method = $uriArr[count($uriArr) - 1];
            $class = $uriArr[count($uriArr) - 2];
            $classDir = implode('\\', array_slice($uriArr, 0, count($uriArr) - 2));
            $class = $namespace . ($classDir ? $classDir . '\\' : '') . $class;
        } else {
            // 参数
            $arguments = '';
            // 方法
            $method = $lastPiece;
            $class = $uriArr[count($uriArr) - 1];
            $classDir = implode('\\', array_slice($uriArr, 0, count($uriArr) - 1));
            $class = $namespace . ($classDir ? $classDir . '\\' : '') . $class;
        }

        // 直接调用方法 不 能指定请求类型
        return array(
            'allow_http_method' => '',
            'class' => $class,
            'method' => $method,
            'arguments' => $arguments ? array($arguments) : array()
        );
    }

    /**
     * 输出错误
     */
    public function showError($msg = 'Invalid Route')
    {
        env() == 'develop' ? raise(404, $msg) : raise(404);
    }

    /**
     * 输出默认首页
     */
    public function showDefault()
    {
        output::flush('Hello Artisan!');
    }
}